#include "buzzlock.h"
#include <errno.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include <stdio.h>
int main() {
    bzz_t lock = malloc(sizeof(bzz));
    init_bzz(lock, 10, (useconds_t) 20000);
    #pragma omp parallel num_threads(100)
    {
        if(syscall(SYS_gettid) % 5 < 1) { 
            bzz_color(BZZ_GOLD, lock);
            bzz_lock(lock);
            int j = 100000;
            while(j--);
            DPRINT(("Gold finished work!\n"));
            bzz_release(lock);
        } else {
            bzz_color(BZZ_BLACK, lock);
            bzz_lock(lock);
            int i = 100000;
            while(i--);
            DPRINT(("Black finished work!\n"));
            bzz_release(lock);
        }
    }
    

    return 0;
}

void init_bzz(bzz_t lock, int num_threads, useconds_t timeout) {
    lock -> timeout = timeout;
    pthread_mutex_init(&lock -> mutex, 0);
    pthread_mutex_init(&lock -> queue_mutex, 0);
    pthread_cond_init(&lock -> black_cond, 0);
    pthread_cond_init(&lock -> old_gold_cond, 0);
    pthread_cond_init(&lock -> new_gold_cond, 0);
    lock -> tbl = create_table(num_threads);
    lock -> in_use = 0;
    lock -> black = 0;
    lock -> old_gold = 0;
    lock -> new_gold = 0;
}

void bzz_color(int color, bzz_t lock) {
    pthread_mutex_lock(&lock -> queue_mutex);
    tbl_insert(&lock -> tbl, syscall(SYS_gettid), color);
    pthread_mutex_unlock(&lock -> queue_mutex);
}

void bzz_lock(bzz_t lock) {
    pthread_mutex_lock(&lock -> queue_mutex);
    int color = tbl_get(lock -> tbl, syscall(SYS_gettid));
    DPRINT(("Trying to aquire lock with color %d\n", color));
    DPRINT(("Thread count\nBlack: %d\nNew Gold: %d\nOld Gold: %d\nIn use: %d\n", lock -> black, lock -> new_gold, lock -> old_gold, lock -> in_use));
    if(color == BZZ_BLACK) {
        
        lock -> black++;
        if(!lock -> in_use && lock -> black == 1 && lock -> new_gold == 0 && lock -> old_gold == 0) {
            pthread_mutex_lock(&lock -> mutex);
            lock -> in_use = 1;
            lock -> black--;
            pthread_mutex_unlock(&lock -> queue_mutex);
            DPRINT(("Black given lock.\n"));
        } else {
            pthread_cond_wait(&lock -> black_cond, &lock -> queue_mutex);
            pthread_mutex_lock(&lock -> mutex);
            lock -> in_use = 1;
            lock -> black--;
            pthread_mutex_unlock(&lock -> queue_mutex);
            DPRINT(("Black aquired lock.\n"));
        }
    } else if(color == BZZ_GOLD) {
        
        struct timespec time;
        time.tv_sec = 0;
        time.tv_nsec = 1000*lock -> timeout;
        lock -> new_gold++;
        if(!lock -> in_use && lock -> black == 0 && lock -> new_gold == 1 && lock -> old_gold == 0) {
            pthread_mutex_lock(&lock -> mutex);
            lock -> in_use = 1;
            lock -> new_gold--;
            pthread_mutex_unlock(&lock -> queue_mutex);
            DPRINT(("New gold given lock.\n"));
        } else if(ETIMEDOUT == pthread_cond_timedwait(&lock -> new_gold_cond, &lock -> queue_mutex, &time)) {
            DPRINT(("Timed out, making new gold old.\n"));
            lock -> new_gold--;
            lock -> old_gold++;
            if(!lock -> in_use && lock -> black == 0 && lock -> new_gold == 0 && lock -> old_gold == 1) {
                pthread_mutex_lock(&lock -> mutex);
                lock -> in_use = 1;
                lock -> old_gold--;
                pthread_mutex_unlock(&lock -> queue_mutex);
                DPRINT(("Old gold given lock.\n"));
            } else {
                pthread_cond_wait(&lock -> old_gold_cond, &lock -> queue_mutex);
                pthread_mutex_lock(&lock -> mutex);
                lock -> in_use = 1;
                lock -> old_gold--;
                pthread_mutex_unlock(&lock -> queue_mutex);
                DPRINT(("Old gold aquired lock.\n"));
            }
        } else {
            pthread_mutex_lock(&lock -> mutex);
            lock -> in_use = 1;
            lock -> new_gold--;
            pthread_mutex_unlock(&lock -> queue_mutex);
            DPRINT(("New gold aquired lock.\n"));
        }
        
    } else {
        DPRINT(("Something weird happened!\n"));
    }
}

void bzz_release(bzz_t lock) {
    pthread_mutex_unlock(&lock -> mutex);
    DPRINT(("Color %d released lock\n", tbl_get(lock -> tbl, syscall(SYS_gettid))));
    pthread_mutex_lock(&lock -> queue_mutex);
    if(lock -> old_gold) {
        pthread_cond_signal(&lock -> old_gold_cond);
    } else if(lock -> black) {
        pthread_cond_signal(&lock -> black_cond);
    } else if(lock -> new_gold) {
        pthread_cond_signal(&lock -> new_gold_cond);
    }
    lock -> in_use = 0;
    pthread_mutex_unlock(&lock -> queue_mutex);
}

